package algorithm;

import dataset.Dataset;
import scicalc.Calculate;
import scicalc.Collection;

import java.util.*;

public class DBSCAN extends AbstractClusterAlgorithm {
    private double epsilon = 0.11;
    private int minPts = 5;
    private List<Integer> Omega = new ArrayList<>();
    private Map<Integer, List<Integer>> nEpsilon = new HashMap<>();

    @Override
    public long fit(Dataset X) {
        long start = System.currentTimeMillis();
        for (int j = 0; j < X.shape[0]; j++) {
            this.nEpsilon.put(j, new ArrayList<>());
            double[] dist = Calculate.sqrt(Calculate.sum(Calculate.pow(Calculate.minus(X.dataset, X.dataset[j]), 2), Calculate.AXIS_1));
            for (int i = 0; i < dist.length; i++) {
                if (dist[i] <= this.epsilon)
                    this.nEpsilon.get(j).add(i);
            }
            if (this.nEpsilon.get(j).size() >= this.minPts)
                this.Omega.add(j);
        }
        int k = 0;
        List<Integer> Gamma = new ArrayList<>();
        for (int i = 0; i < X.shape[0]; i++) {
            Gamma.add(i);
        }
        Map<Integer, List<Integer>> clusters = new HashMap<>();
        while (!this.Omega.isEmpty()) {
            List<Integer> GammaOld = Collection.union(new ArrayList<>(), Gamma);
            Queue<Integer> Q = new LinkedList<>();
            int o = this.Omega.get(Calculate.randomInt(0, this.Omega.size()));
            Q.offer(o);
            Gamma = Collection.difference(Gamma, o);
            while (!Q.isEmpty()) {
                int q = Q.poll();
                if (this.nEpsilon.get(q).size() >= this.minPts) {
                    List<Integer> Delta = Collection.intersection(this.nEpsilon.get(q), Gamma);
                    for (Integer i : Delta)
                        Q.offer(i);
                    Gamma = Collection.difference(Gamma, Delta);
                }
            }
            clusters.put(k, Collection.difference(GammaOld, Gamma));
            this.Omega = Collection.difference(this.Omega, clusters.get(k++));
        }
        clusters.put(-1, Gamma);
        this.labels = new int[X.shape[0]];
        for (int i = -1; i < k; i++) {
            for (Integer j : clusters.get(i)) {
                this.labels[j] = i;
            }
        }
        this.result.shape[0] = X.shape[0];
        this.result.shape[1] = X.shape[1];
        result.dataset = X.dataset;
        result.label = labels;
        return System.currentTimeMillis() - start;
    }

    public void setEpsilon(double epsilon) {
        this.epsilon = epsilon;
    }

    public void setMinPts(int minPts) {
        this.minPts = minPts;
    }
}
